// Copyright (c) 2009-2010, Jeremy Brewer
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package galaxie500.io;

import galaxie500.exceptions.IOError;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.StringTokenizer;

/**
 * Class for reading text files with space delimited columns
 * 
 * This class aims to make reading columns from text files both simple and
 * efficient. Because we expect to read all data from disk in this format,
 * encapsulating this logic in a class makes improving performance simpler.
 * 
 * @author Jeremy Brewer
 */
public class ColumnReader {

  private BufferedReader reader;
  private String line = "";
  private String[] columns;
  private int maxNumColumns;
  private int numColumns = 0;

  /**
   * Creates a new instance for reading from the given reader. Columns beyond
   * the maximum number of columns will be ignored.
   * 
   * Example Usage:
   * 
   * <pre>
   * ColumnReader reader =
   *     new ColumnReader(new BufferedReader(new FileReader(&quot;foo.dat&quot;)));
   * try {
   *   while (reader.readLine()) {
   *     String[] columns = reader.getColumns();
   *     // NOTE: the loop is not over columns.length.
   *     for (int i = 0; i &lt; reader.getNumColumns(); i++) {
   *       // Do something with columns[i].
   *     }
   *   }
   * } catch (IOException e) {
   *   e.printStackTrace();
   * } finally {
   *   reader.close();
   * }
   * </pre>
   * 
   * @param reader A reader for the input source
   * @param maxNumColumns The maximum number of columns on a line
   */
  public ColumnReader(BufferedReader reader, int maxNumColumns) {
    setMaxNumColumns(maxNumColumns);
    this.reader = reader;
  }

  // Accessors.

  /**
   * @return The number of columns read on the last call to readLine().
   */
  public int getNumColumns() {
    return numColumns;
  }

  /**
   * @param maxNumColumns The maximum number of columns
   */
  public void setMaxNumColumns(int maxNumColumns) {
    assert maxNumColumns > 0 : "Invalid maxNumColumns: " + maxNumColumns;
    this.maxNumColumns = maxNumColumns;
    this.columns = new String[maxNumColumns];
    resetColumns();
  }

  /**
   * Resets columns to be empty strings.
   */
  private void resetColumns() {
    numColumns = 0;
    for (int i = 0; i < columns.length; i++) {
      columns[i] = "";
    }
  }

  /**
   * @return The maximum number of columns allowed on a line
   */
  public int getMaxNumColumns() {
    return maxNumColumns;
  }

  /**
   * @return The last read line, not including the trailing newline
   */
  public String getLine() {
    return line;
  }

  /**
   * Reads the next line of input and parses the line such that all white space
   * delimited columns are stored in the array accessible by getColumns().
   * 
   * @throws IOError If an IO error occurs
   * @return True if more lines remain, false otherwise.
   */
  public boolean readLine() {
    // Reset old values.
    resetColumns();

    try {
      line = reader.readLine();
    } catch (IOException e) {
      throw new IOError(e.getMessage());
    }

    if (line == null) {
      // End of input.
      line = "";
      return false;
    }

    StringTokenizer tokenizer = new StringTokenizer(line);

    int i = 0;
    while (i < maxNumColumns && tokenizer.hasMoreTokens()) {
      columns[i] = tokenizer.nextToken();
      i++;
    }
    numColumns = i;
    return true;
  }

  /**
   * Returns the columns read on the last call to readLine().
   * 
   * @return The current columns read from the file.
   */
  public String[] getColumns() {
    return columns;
  }

  /**
   * Closes the underlying reader.
   * 
   * @throws IOError If the reader can't be closed
   */
  public void close() {
    try {
      reader.close();
    } catch (IOException e) {
      throw new IOError(e.getMessage());
    }
  }

  /**
   * Closes the underlying reader.
   */
  @Override
  public void finalize() {
    close();
  }
}
